#!/usr/bin/env python
# -*- coding:utf-8 -*-
__author__ = 'walkingsky'

import argparse
from models.fund_service import FundService
import os,time
import pandas as pd
import numpy as np
from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score
from sklearn.metrics import mean_squared_error
from models.utils import print_aligned
from datetime import datetime,timedelta,timezone
import requests
from contextlib import contextmanager

from config.data_config import FUND_COL_NAMES,COLUMNS_TO_ANALYZE,FUND_PRINT_NAMES, \
        COLUMNS_TO_ANALYZE_DAYS,FUND_KINDS,FUND_LJSYLZS_NAME,FUND_CACHE_FILE_PATH 



def calculate_r2_ljsyl_fix(file:str,col_num:int=5,max_rates:int=20):
    '''
        分析各基金的增长率线性度，通过线性回归计算 R² 和 MSE 值来评估哪只基金的增长率更接近直线
    '''
    if os.path.exists(file):
                df = pd.read_csv(file,dtype={'基金代码':str})
    else:
        print('文件不存在，请先用-g参数获取数据')
        return

    # 定义时间点（单位：天）
    # 时间周期（以天为单位）
    periods = np.array(COLUMNS_TO_ANALYZE_DAYS)

    period_names = COLUMNS_TO_ANALYZE
    # 创建线性回归模型
    model = LinearRegression()

    # 计算每个基金的R²
    results = []
    for _, row in df.iterrows():
        # 获取基金名称
        fund_name = row['基金名称']
        fund_code = row['基金代码']
        
        # 获取增长率数据，跳过缺失值和NaN
        growth_rates = []
        valid_period_indices = []
        for i, name in enumerate(period_names):
            # 跳过多余数据
            if i == col_num:
                break
            value = row[name]
            if value is not None and not np.isnan(value):
                growth_rates.append(value)
                valid_period_indices.append(i)
        # print(periods)
        # print(valid_period_indices)        
        valid_periods = periods[valid_period_indices]
        
        # 确保有足够的数据点进行线性回归
        # 去掉 不再增长率区间的数据，区间为max_rates-5到max_rates之间，大于60的单算
        if max_rates >= 60:
            if len(growth_rates) < col_num \
                or growth_rates[-1] < max_rates:
                #print(f"跳过 {fund_name}：有效数据点不足")
                continue
        else:
            if len(growth_rates) < col_num \
                or (growth_rates[-1] > max_rates or growth_rates[-1] < (max_rates -5)):
                #print(f"跳过 {fund_name}：有效数据点不足")
                continue
        
        #准备数据
        x = valid_periods.reshape(-1, 1)    
        y = np.array(growth_rates)
        
        # 训练模型并计算R²（使用模型的score方法）
        # 线性回归
        model.fit(x, y)

        # 预测值
        y_pred = model.predict(x)
        #计算R^2
        r2 = r2_score(x, y_pred)  # 
        #计算
        mse = mean_squared_error(y, y_pred)
        
        # 去掉水平直线
        #if r2 == 1 and mse == 0:
        #    continue
        
        results.append({
            '基金名称': row['基金名称'],
            '代码':fund_code,
            'R²': r2,
            'MSE': mse,
            '斜率': model.coef_[0],
            '截距': model.intercept_,
            '增长率': growth_rates,
            '使用的周期': [period_names[i] for i in valid_period_indices]
        })
    
    print(f"筛选出{len(results)}条数据")
    if len(results) == 0:
        return

    # 转换为DataFrame并排序
    results_df = pd.DataFrame(results).sort_values('R²', ascending=False).reset_index(drop=True)

    # 输出结果
    # print("各基金增长率线性拟合结果（按R²排序）：")
    # print(results_df.to_string(index=False))

    # 打印线性度最好的前5只基金
    print("按R²排序的前10只基金（增长率最接近直线）：")
    print_aligned(results_df.head(10))
    '''
    for _, row in results_df.head(5).iterrows():
        print(f"基金名称: {row['基金名称']}, 代码: {row['代码']}")
        print(f"R²: {row['R²']:.4f}, MSE: {row['MSE']:.4f}")
        print(f"增长率: {np.round(row['增长率'], 2)}")
        print(f"使用的周期: {row['使用的周期']}")
        print(f"拟合直线: y = {row['斜率']:.6f}x + {row['截距']:.6f}")
        print("-" * 50)
    '''

def get_fund_names(fund_type:str):
    '''
    获取fund_type类型基金 代码-》名称的对应关系
    返回:{代码1:名称1，代码2:名称2}
    '''
    file = FUND_CACHE_FILE_PATH + '开放基金数据'+ fund_type + '.csv'
    if not os.path.exists(file):
        print('文件不存在')
        return {}
    
    df_src = pd.read_csv(file,dtype={'基金代码':str})
    result = {}
    for _,row in df_src.iterrows():
        result[row['基金代码']] = row['基金名称']
    
    return result

def get_fund_guimo(fund_type:str):
    '''
    获取fund_type类型基金 代码-》规模的对应关系
    返回:{代码1:规模1，代码2:规模2}
    '''
    file = FUND_CACHE_FILE_PATH + '开放基金信息-'+ fund_type + '.csv'
    if not os.path.exists(file):
        print('文件不存在')
        return {}
    
    df_src = pd.read_csv(file,dtype={'基金代码':str})
    # 确保日期列是 datetime 类型，以便正确比较大小
    df_src['日期'] = pd.to_datetime(df_src['日期'])

    # 按基金代码分组，每组中筛选出日期最大的记录
    # 然后转换为字典，键为基金代码，值为对应的规模(亿)
    result = (
        df_src.sort_values('日期')  # 按日期排序
        .groupby('基金代码').tail(1)  # 每组取最后一条（日期最大）
        .set_index('基金代码')['规模(亿)']  # 选取需要的列
        .to_dict()  # 转换为字典
    )
    
    return result



def filter_sort(file:str,condiston_str:str,sort_order:bool=True):
    '''
        对基金数据，进行条件筛选和某列的排序

        参数：
        condiston_str:str 类似,大15,大15,小15,大15,小15，对应["日增长率","近1周", "近1月", "近3月", "近6月", "近1年"]
        列的排序，和过滤条件
        sort_order:bool 排序给则，True为正序，False为倒序
    '''
    print(f'对{file}数据，进行条件"{condiston_str}"筛选和某列的{"正序" if sort_order else "倒序"}排序')
    if os.path.exists(file):
        df = pd.read_csv(file,dtype={'基金代码':str})
    else:
        print('文件不存在，请先用-g参数获取数据')
        return
    
    df = pd.read_csv(file,dtype={'基金代码':str})
    # 解析条件字符串
    condition_str = condiston_str
    conditions = condition_str.split(',')

    # 对应的列名
    columns = COLUMNS_TO_ANALYZE

    # 构建过滤条件
    filter_conditions = pd.Series([True] * len(df))  # 初始全部为True

    # 构建排序依据
    sort_columns = []

    for col, cond in zip(columns, conditions):
        if cond.strip() != '':
            # 处理过滤条件（如 >15）
            operator = cond[0]  # 提取操作符
            value = float(cond[1:])  # 提取值
            print(value)
            
            if operator == '大':
                filter_conditions &= df[col] > value
            elif operator == '小':
                filter_conditions &= df[col] < value
            elif operator == '等':
                filter_conditions &= df[col] == value
        else:
            # 空条件表示作为排序依据（倒序）
            sort_columns.append(col)

    # 应用过滤条件
    filtered_df = df[filter_conditions]

    # 应用排序
    if sort_columns:
        filtered_df = filtered_df.sort_values(by=sort_columns, ascending=sort_order)

    print_aligned(filtered_df[FUND_PRINT_NAMES])
    '''
    # 查看数据集行数和列数
    rows, columns = filtered_df.shape

    if rows > 0 and columns > 0:
        # 数据非空时展示数据前几行信息
        print(f"\n数据前几行内容信息：")
        print(filtered_df.head().to_csv(sep='\t', na_rep='nan'))
    else:
        # 数据为空时展示数据全部内容信息
        print(f"\n数据全部内容信息：")
        print(filtered_df.to_csv(sep='\t', na_rep='nan')) 
    '''

def get_datas(file:str,kind:str,dx:str):
    '''
        获取基金数据并写入csv文件
        参数：
        kind:str 基金类型
        dx:str 基金是否可以购买过滤选项,'0'为全部基金，'1'为可购买基金

    '''
    print(f"获取{kind}类型基金排行数据({'全部基金' if dx == '0' else '可购买基金'})，并写入{file}")
    # 是否可购买参数设置
    if dx == '0':
        fund_data = FundService().get_fundranking(kind=kind,duration='rzzf',num=10000,get_all=True,dx='0')
    else:
        fund_data = FundService().get_fundranking(kind=kind,duration='rzzf',num=10000,get_all=True)

    # 将列表转换为 DataFrame
    df = pd.DataFrame([row.split(',') for row in fund_data], columns=FUND_COL_NAMES)
    # 处理空字符串为 NaN
    df = df.replace('', float('nan'))

    df.to_csv(file ,mode='w',header=True,index=False)

def get_ljsyzs_2csv(fund_type:str='gp',index_code:str='000300',duration_type:str='hy'):
    '''
        按照固定的分类和时间段获取基金的历史收益走势数据，写入csv文件
    '''
    from config.data_config import FUND_INDEXCODE_ARR,FUND_DURATION_TYPE_ARR
    #参数验证
    if ( not index_code in list(FUND_INDEXCODE_ARR.keys())) \
            or (not duration_type in list(FUND_DURATION_TYPE_ARR)) \
            or (not fund_type in FUND_KINDS):
        print('参数不正确')
        return False
        
    # 读取基金列表文件
    file = FUND_CACHE_FILE_PATH + '开放基金数据'+ fund_type + '.csv'
    if not os.path.exists(file):
        print('文件不存在')
        return
    print(f"获取{fund_type}类型的基金{FUND_DURATION_TYPE_ARR[duration_type]['name']}收益走势数据，写入{file}")
    df_src = pd.read_csv(file,dtype={'基金代码':str})
    
    
    columns=['基金代码', '基金名称']
    #创建时间戳列
    i = FUND_DURATION_TYPE_ARR[duration_type]['value']
    today = datetime.date.today()
    while i>0:
        date_ob = today - datetime.timedelta(days=i)
        columns.append(str(int(time.mktime(date_ob.timetuple()))))
        i = i - 1
    #创建空的df 数据结构
    df = pd.DataFrame(columns=columns)

    #设置第一次执行变量，为了在第一次执行中获取一次index的对比数据
    first = True
    headers = {}
    headers["Referer"] = 'https://fund.eastmoney.com/'
    headers["Host"] = 'api.fund.eastmoney.com'
    # 释放连接，缓解服务器压力
    # headers["Connection"] = 'close'
    with one_sessions(headers) as (session):
        for _, row in df_src.iterrows():
            print(f"获取{row['基金名称']}的历史收益走势数据")
            res = FundService().get_ljsyzs(session=session,fund_code=row['基金代码'],index_code=index_code ,duration_type=duration_type)
            time.sleep(0.5)
            
            for data in res: 
                name = data['name']
                # 如果不是第一次跳过不是基金数据的数据行
                if first == False and name in FUND_LJSYLZS_NAME:
                    break

                # 构建行数据
                if first and name in FUND_LJSYLZS_NAME and name != '同类平均':
                    col_data = {
                        '基金代码': index_code,
                        '基金名称': FUND_INDEXCODE_ARR[index_code]
                    }
                elif not name in FUND_LJSYLZS_NAME:
                    col_data = {
                        '基金代码': row['基金代码'],
                        '基金名称': row['基金名称']
                    }
                else:
                    continue

                # 遍历数据    
                for point in data['data']:
                    timestamp = int(point[0] /1000)  # 时间戳（秒级）
                    value = float(point[1])      # 对应的值

                    col_data[str(timestamp)] = value
                
                df.loc[len(df)] = col_data

            first = False  
         
    # 显示前10行验证结果
    # 计算阈值：至少需要50%的非空值
    threshold = len(df) * 0.5  # 行数的50%

    # 删除空值超过50%的列
    df = df.dropna(axis=1, thresh=threshold)     
    # print(df.head(10))

    df.to_csv(FUND_CACHE_FILE_PATH + '历史收益走势数据-' + fund_type + '.csv',index=False)



def calculate_r2_ljsyl(fund_type:str='gp',min_value:int=2,max_value:int=0,from_days:int=0,show_number:int=10):
    '''
        按照基金类型，计算哪只基金的历史收益走势更稳定（接近直线）
        参数：
        fund_type:str 分类名称
        from_days:统计多少天内的数据
    '''

    file = FUND_CACHE_FILE_PATH + '历史收益走势数据-' + fund_type + '.csv'
    if not os.path.exists(file):
        print('文件不存在')
        return
    df = pd.read_csv(file,dtype={'基金代码':str})

    print(f"类型{fund_type}，计算{from_days}内基金收益走势，条件最后收益>{str(min_value)},<{str(max_value)}")

    #  提取时间戳列（排除前两列）
    timestamp_columns_tmp = df.columns[3:]
    timestamp1 = int(timestamp_columns_tmp[-1])
    timestamp_columns = []
    for k in timestamp_columns_tmp:
        # 转换时间戳为datetime对象（UTC时间）
        timestamp2 = int(k)
        dt1 = datetime.fromtimestamp(timestamp1, tz=timezone.utc)
        dt2 = datetime.fromtimestamp(timestamp2, tz=timezone.utc)
        
        # 提取日期部分（忽略时分秒）
        date1 = dt1.date()
        date2 = dt2.date()
        
        # 计算天数差（取绝对值确保结果非负）
        days_diff = abs((date2 - date1).days)

        if days_diff > from_days:
            continue
        timestamp_columns.append(k)

    #  计算每条曲线的R²值
    r2_results = []
    for idx, row in df.iterrows():
        # 提取有效数据点（排除空值）
        y_values = []
        x_values = []
        for t_idx, t in enumerate(timestamp_columns):
            value = row[t]
            if pd.notna(value):  # 只保留非空值
                y_values.append(value)
                x_values.append(t_idx)  # 用时间戳的索引作为x（代表时间顺序）
        
        #跳过空
        if len(y_values) < 3:
            continue
        '''
            #放弃这个条件
            # 计算第一个数和最后一个数的差值
            value = int((y_values[-1] - y_values[0])*100)

            # 不再设定的增长区间里就跳过该数据
            if value <min_value or value >max_value:
                continue
        '''

        # 不再设定的增长区间里就跳过该数据
        if not (max_value == 0 and min_value == 0):
            if y_values.min() <min_value or y_values.max() >max_value:
                continue
        
        # 线性回归拟合
        if len(x_values) >= 2:  # 至少需要2个点才能拟合
            x = np.array(x_values).reshape(-1, 1)
            y = np.array(y_values)
            model = LinearRegression()
            model.fit(x, y)
            y_pred = model.predict(x)
            r2 = r2_score(y, y_pred)
            mse = mean_squared_error(y, y_pred)
            sse = np.sum((y - y_pred) **2)  # 残差平方和

            r2_results.append({
                '基金代码': row['基金代码'],
                '基金名称': row['基金名称'],
                '数据点数量': len(x_values),
                'R²值': round(r2, 4),
                'MSE': mse,
                'sse 残差平方和':sse,
                '斜率': model.coef_[0],
                '截距': model.intercept_,
                })
        else:
            r2_results.append({
                '基金代码': row['基金代码'],
                '基金名称': row['基金名称'],
                '数据点数量': len(x_values),
                'R²值': None
            })
    print(f"筛选出{len(r2_results)}条数据")
    if len(r2_results) == 0:
        return
    #  展示结果并排序
    result_df = pd.DataFrame(r2_results)
    result_df = result_df.sort_values(by='R²值', ascending=False)

    # 把结果写入文件，防止多次查询
    result_df.to_excel(f"{FUND_CACHE_FILE_PATH}累计增长率统计结果-{fund_type}-{from_days}天.xlsx",index=False)

    print("各曲线线性拟合优度（R²值，越接近1越接近直线）：")
    print_aligned(result_df.head(show_number))

def calculate_max_drawdown(group,value_column):
    """计算单个基金分组的最大回撤"""
    # 确保数据按日期排序
    group_sorted = group.sort_values('日期').reset_index(drop=True).copy()
    # 获取净值序列（假设列名为'单位净值'，根据实际情况修改）
    nav_series = group_sorted[value_column]
    
    # 计算累计最高净值（到每个时间点的历史最高点）
    cumulative_max = nav_series.cummax()
    # 计算每个时间点的回撤率
    drawdown = (nav_series - cumulative_max) / cumulative_max
    # 最大回撤为回撤率的最小值（最负的值）
    max_drawdown = drawdown.min()
    max_dd_index = drawdown.idxmin()  # 获取最小回撤率的索引
    
    return [max_drawdown,max_dd_index+1]

def calculate_r2_lsjz(fund_type: str = 'hh', min_value: float = 0, max_value: float = 0, 
                      from_days: int = 60, value_column: str = 'DWJZ',
                      show_number:int = 10, max_guimo: float = 1):
    '''
        按照基金类型，计算哪只基金的历史净值走势更稳定（接近直线）
        参数：
        fund_type: str 分类名称
        from_days: 统计多少天内的数据
        value_column: 用于分析的列，'DWJZ'（单位净值）或 'LJJZ'（累计净值）
        show_number: 结果展示的数据条数
        max_guimo: 限定基金规模
    '''
    
    # 验证输入的列是否有效
    if value_column not in ['DWJZ', 'LJJZ']:
        print(f"错误：无效的列名 {value_column}，请使用 'DWJZ' 或 'LJJZ'")
        return

    # 构建数据文件路径
    file = f"{FUND_CACHE_FILE_PATH}历史净值数据-{fund_type}.csv"
    if not os.path.exists(file):
        print(f'文件不存在: {file}')
        return
    
    # 读取数据并转换日期格式
    df = pd.read_csv(file, dtype={'基金代码': str})
    try:
        df['日期'] = pd.to_datetime(df['日期'])  # 转换为日期类型
    except ValueError:
        print("错误：日期格式解析失败，请检查数据文件")
        return

    print(f"类型{fund_type}，计算{from_days}天内基金{value_column}走势，条件为收益率>{min_value},<{max_value}")

    
    r2_results = []
    # 获取基金代码对应的基金名称
    fund_names = get_fund_names(fund_type)

    # 获取基金代码对应最新的最新规模数据
    fund_guimos = get_fund_guimo(fund_type)

    # 按日期排序
    group_sorted = df.sort_values('日期').copy()
    
    # 获取最新日期作为参考点
    latest_date = df['日期'].max()
    # 筛选出指定天数内的数据
    date_diff = (latest_date - group_sorted['日期']).dt.days
    recent_data = group_sorted[date_diff <= from_days]

    # 按基金代码和名称分组处理
    grouped = recent_data.groupby(['基金代码'])    
    
    for (fund_code), group in grouped:       
        '''
        # 按日期排序
        group_sorted = group.sort_values('日期').copy()
        
        # 筛选出指定天数内的数据
        date_diff = (latest_date - group_sorted['日期']).dt.days
        recent_data = group_sorted[date_diff <= from_days]
        '''
        # 提取有效的净值数据

        values = group[value_column].dropna()
        dates = group['日期'].loc[values.index]  # 对应的日期
        
        # 数据点不足的跳过
        if len(values) < 3:
            continue

        # 取分组后第一个数据的"基金名称"列值
        #fund_name = group['基金名称'].iloc[0]
        if fund_code in fund_names.keys():
            fund_name = fund_names[fund_code]
        else:
            fund_name = ''

        if fund_code in fund_guimos.keys():
            fund_guimo = fund_guimos[fund_code]
            if fund_guimo < max_guimo:
                continue #跳过小于限定规模的数据
        else:
            fund_guimo = '--' #
                
        
        # 检查是否小于最小值或大于最大值
        if not (min_value == 0 and max_value == 0):
            if values.min() < min_value or values.max() > max_value:
                continue  # 跳过不符合条件的值

        
        # 准备线性回归分析（使用索引作为x值代表时间顺序）
        x_values = np.arange(len(values)).reshape(-1, 1)
        y_values = values.values
        
        model = LinearRegression()
        model.fit(x_values, y_values)
        y_pred = model.predict(x_values)
        
        # 计算评估指标
        r2 = r2_score(y_values, y_pred)
        mse = mean_squared_error(y_values, y_pred)
        sse = np.sum((y_values - y_pred) ** 2)
        # 增长率 （小数表示）
        rate_rise = (y_values[-1]-y_values[0])/y_values[0] 
        # 年化收益率（小数）(考虑复利情况)
        # annualized = (1 + rate_rise) ** (365/from_days) - 1
        # 按实际天数算
        annualized = rate_rise * (365/(dates.max() - dates.min()).days)
        max_drawdown = calculate_max_drawdown(group,value_column)
        r2_results.append({
            '基金代码': fund_code,
            '基金名称': fund_name,
            '数据点数量': len(values),
            'R²值': round(r2, 4),
            'MSE': round(mse, 6),
            '残差平方和': round(sse, 6),
            '斜率': round(model.coef_[0], 6) ,
            '截距': round(model.intercept_, 6),
            '最大值': y_values.max(),
            '最小值': y_values.min(),
            '最大回撤率': max_drawdown[0] * 100,
            '最大回撤位置': max_drawdown[1],
            '增长率': rate_rise * 100,
            '年化收益率': annualized * 100,
            '规模(亿)': fund_guimo,
            '起始日期': dates.min().strftime('%Y-%m-%d'),
            '结束日期': dates.max().strftime('%Y-%m-%d')
        })
    
    print(f"筛选出{len(r2_results)}条数据")
    if len(r2_results) == 0:
        return
    

    # 排序并展示结果
    result_df = pd.DataFrame(r2_results)
    result_df = result_df.sort_values(by='R²值', ascending=False)

    # 把结果写入文件，防止多次查询
    result_df.to_excel(f"{FUND_CACHE_FILE_PATH}历史净值统计结果-{fund_type}-{value_column}-{from_days}天.xlsx",index=False)
    
    print(f"各基金{value_column}走势线性拟合优度（R²值，越接近1越接近直线）：")
    print_aligned(result_df.head(show_number))

def batch_print_r2():
    '''
        连续执行基金增长率分析，并打印
    '''
    # 连续执行基金增长率分析    
    for test_kind in FUND_KINDS: 
        if test_kind == 'all':
            continue
        print(f"\n\n-------------基金类型{test_kind}--------------")
        fund_data_file = FUND_CACHE_FILE_PATH + '开放基金数据' + test_kind + '.csv'       
        for j in [5,10,15,20,25,30,35,40,45,50,55]:
            for i in [3,4,5]:
                # 计算
                print(f"\n\n-------------基金类型{test_kind}-{COLUMNS_TO_ANALYZE[i]}增长率 {str(j-5)}-{str(j)}%--------------")
                calculate_r2_ljsyl_fix(file=fund_data_file,max_rates=j,col_num=i)

        for i in [4,5]: 
            print(f"\n\n-------------基金类型{test_kind}-{COLUMNS_TO_ANALYZE[i]}增长率大于60%--------------")            
            calculate_r2_ljsyl_fix(file=fund_data_file,max_rates=60,col_num=i)       

@contextmanager
def two_sessions(headers1:dict=None,headers2:dict=None):
    """上下文管理器：创建并释放两个域名的专用Session"""
    # 为api1.aaa.com创建Session
    session1 = requests.Session()
    # 为api2.aaa.com创建Session
    session2 = requests.Session()
    
    try:
        # 配置公共请求头（可选）
        common_headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
            "Accept" : "*/*",
            "Accept-Language" : "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7",
            "Connection" : "keep-alive",
            #"Referer" : refer_url,
            #"Cookie" : "ASP.NET_SessionId=fn33e1v1horkjpvmjdsdtrnu",
            "Accept-Encoding":"gzip, deflate",
        }


        session1.headers.update({** common_headers , ** headers1})
        session2.headers.update({** common_headers , ** headers2})
        
        # 返回两个Session供外部使用
        yield (session1, session2)
    finally:
        # 确保Session关闭，释放连接
        session1.close()
        session2.close()

@contextmanager
def one_sessions(headers:dict=None):
    """上下文管理器：创建并释放一个域名的专用Session"""
    # 为api1.aaa.com创建Session
    session1 = requests.Session()
    
    try:
        # 配置公共请求头（可选）
        common_headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
            "Accept" : "*/*",
            "Accept-Language" : "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7",
            "Connection" : "keep-alive",
            #"Referer" : refer_url,
            #"Cookie" : "ASP.NET_SessionId=fn33e1v1horkjpvmjdsdtrnu",
            "Accept-Encoding":"gzip, deflate",
        }
        session1.headers.update({** common_headers , ** headers})        
        
        # 返回两个Session供外部使用
        yield (session1)
    finally:
        # 确保Session关闭，释放连接
        session1.close()        

def batch_update_fund_data(fund_type:str='gp'):
    """
    批量更新基金历史数据到CSV文件
    
    参数:
        fund_codes: 基金代码列表
        csv_file_path: CSV文件保存路径
    """
    
    # 读取基金列表文件
    file = FUND_CACHE_FILE_PATH + '开放基金数据'+ fund_type + '.csv'
    if not os.path.exists(file):
        print('文件不存在')
        return
    
    print(f"获取{fund_type}类型的基金历史净值势数据，写入{file}")
    df_src = pd.read_csv(file,dtype={'基金代码':str})    
    
    headers1 = {}
    headers2 = {}
    headers1["Referer"] = f'https://fund.eastmoney.com/'
    headers1["Host"] = 'fund.eastmoney.com'

    headers2["Referer"] = 'https://fund.eastmoney.com/'
    headers2["Host"] = 'api.fund.eastmoney.com'

    
    fund_names = get_fund_names(fund_type=fund_type)
    with two_sessions(headers1,headers2) as (session1,session2):
        for _, row in df_src.iterrows():
            #判断基金类型pd文件是否存在
            # 检查文件是否存在
            code = row['基金代码']
            #fund_name = row['基金名称']
            fund_name = [code]
            csv_file_path = FUND_CACHE_FILE_PATH + '历史净值数据-' + fund_type + '.csv'
            file_exists = os.path.exists(csv_file_path)

            last_date = None  # 存储每个基金的最后记录日期
            
            # 如果文件存在，读取现有数据中的最后日期
            if file_exists:
                try:
                    # 读取CSV文件
                    df_existing = pd.read_csv(csv_file_path,dtype={'基金代码':str})
                    
                    # 确保FUND_CODE列存在
                    if '基金代码' not in df_existing.columns:
                        print("CSV文件格式不正确")
                        return
                                                    
                    # 筛选该基金的记录
                    fund_data = df_existing[df_existing['基金代码'] == code]
                    if not fund_data.empty:
                        # 获取最后日期
                        last_date = fund_data['日期'].max()
                        print(f"基金 {fund_name} 已存在数据，最后记录日期: {last_date}")
                    else:
                        last_date = None
                        print(f"基金 {fund_name} 不存在历史数据，将获取全部数据")
                            
                except Exception as e:
                    print(f"读取现有CSV文件时出错: {str(e)}")
                    return
        
            # 处理每个基金
            
            print(f"开始处理基金: {fund_name}")
            
            # 确定开始日期（最后日期的第二天）
            start_date = None
            if last_date:
                try:
                    # 将最后日期转换为datetime对象并加一天
                    last_date_obj = datetime.strptime(last_date, '%Y-%m-%d')
                    next_day = last_date_obj + timedelta(days=1)
                    start_date = next_day.strftime('%Y-%m-%d')
                    print(f"将获取 {start_date} 之后的新数据")
                except Exception as e:
                    print(f"处理日期时出错: {str(e)}，将获取全部数据")
                    start_date = None

            if last_date == None or start_date == None:
                start_date = (datetime.now() - timedelta(days=365)).strftime('%Y-%m-%d')
            
            # 获取基金历史数据
            if last_date == None:
                history_data = FundService().get_fund_info(session1,code, start_date)
            else:
                history_data = FundService().get_lsjz(session2,code, start_date)
            
            if not history_data:
                print(f"基金 {fund_name} 没有新数据需要更新\n")
                continue
            
            print(f"基金 {fund_name} 获取到 {len(history_data)} 条新数据\n")
            
            # 转换为DataFrame并添加基金代码列
            df_new = pd.DataFrame(history_data)
            df_new.rename(columns={'FSRQ': '日期'}, inplace=True)
            df_new['基金代码'] = code  # 添加基金代码列
            # df_new['基金名称'] = fund_name # 添加基金名称列
            
            # 追加到CSV文件
            try:
                # 如果是新文件，写入表头；否则不写表头
                df_new.to_csv(csv_file_path, mode='a', header=not file_exists, index=False)
                print(f"基金 {fund_name} 的数据已成功追加到CSV文件")
                
                # 首次写入后更新文件存在状态
                if not file_exists:
                    file_exists = True
            except Exception as e:
                print(f"写入CSV文件时出错: {str(e)}")

            time.sleep(2)
    
    print("\n所有基金数据更新完成") 


def main():
    print('开始执行...')
    # 1. 创建解析器
    parser = argparse.ArgumentParser(
        description="分析、处理a股基金数据的脚本",  # 脚本描述
        formatter_class=argparse.ArgumentDefaultsHelpFormatter  # 显示默认值
    )    

    # 2. 添加参数（按功能分组，更清晰）

    #基金类型
    kind_group = parser.add_argument_group("获取开放基金的类型")
    kind_group.add_argument(
        "--kind","-k",
        type=str,
        choices=FUND_KINDS,
        default='all',
        help="设置获取基金的类型，all:全部 gp:股票型基金 zs:指数型 hh:混合型 zq:债券型 qdii: QDII fof: FOF"
    )

    # 获取基金数据参数组
    get_group = parser.add_argument_group("获取基金最新数据")
    get_group.add_argument(
        "--get","-g", 
        action="store_true", 
        help="获取基金最新数据"
    )

    get_group.add_argument(
        "--dx", 
        type=str,
        default='1',
        help="设置获取“可购买”基金还是“全部基金” '0'为全部基金，'1'为可购买基金"
    )    

    data_group = parser.add_argument_group("列表展示基金数据")
    data_group.add_argument(
        "--show", "-s",
        action="store_true", 
        help="展示基金数据"
    )

    data_group.add_argument(
        "--max-min", 
        action="store_true", 
        help="展示基金数据拍行列的最大值、最小值"
    )

    data_group.add_argument(
        "--filter","-f", 
        type=int,
        default=1,
        choices=[1,2], 
        help="排序方法，1：正序，2：倒序"
    )

    data_group.add_argument(
        "--filter-con","-fc", 
        type=str,
        default="", 
        help="根据条件，按照设定列排序数据，各列筛选的条件： \
            \t 5个逗号分隔6个列的条件，空为要排序的列，大于或小于号+数字为条件，比如',大15,大15,小15,大15,小15'"
    )

    data_group.add_argument(
        "--sort-by-r2","-r", 
        action="store_true", 
        help="分析各基金的增长率线性度，通过线性回归计算 R² 和 MSE 值来评估哪只基金的增长率更接近直线"
    )

    data_group.add_argument(
        "--r2-num","-rn", 
        type=int, 
        help="分析各基金的增长率线性度，必须的列数，最小2，最大6 ['日增长率','近1周', '近1月', '近3月', '近6月', '近1年']"
    )
    data_group.add_argument(
        "--r2-rates","-rr", 
        type=int, 
        help="分析各基金的增长率线性度，最后一列的最小增长率 单位%%"
    )

    data_group.add_argument(
        "--test","-t", 
        action="store_true", 
        help="测试: 连续执行基金增长率分析"
    )

    

    history_group = parser.add_argument_group("基金历史数据")
    history_group.add_argument(
        "--history", "-hs",
        action="store_true", 
        help="进入基金历史数据"
    )

    history_group.add_argument(
        "--his-get", "-hsg",
        action="store_true", 
        help="获取基金历史累计收益数据"
    )

    history_group.add_argument(
        "--dtype",
        type=str,
        choices=['m','q','hy','y'],
        default='y', 
        help="设置获取数据时的时间类型 'm'一个月,'q'3个月,'hy'半年,'y'一年"
    )

    history_group.add_argument(
        "--get-jz", "-gjz",
        action="store_true", 
        help="获取基金历史净值数据"
    )

    history_group.add_argument(
        "--days",
        type=int,
        default=60, 
        help="分析数据时设置n天内的数据"
    )

    history_group.add_argument(
        "--show-number","-n",
        type=int,
        default=10, 
        help="分析数据的结果展示几条数据"
    )

    history_group.add_argument(
        "--jz-r2",
        action="store_true", 
        help="分析历史净值数据"
    )

    history_group.add_argument(
        "--jz-lj",
        action="store_true", 
        help="是否是累计净值：开启选项为累计净值，否则为单位净值"
    )

    history_group.add_argument(
        "--max-value","-maxv",
        type=float,
        default=0, 
        help="分析数据时设置过滤的最大收益率的值，单位%%"
    )

    history_group.add_argument(
        "--min-value","-minv",
        type=float,
        default=0, 
        help="分析数据时设置过滤的最大收益率的值，单位%%"
    )

    history_group.add_argument(
        "--max-guimo",
        type=float,
        default=1, 
        help="分析数据时设置过滤的最大基金规模，单位亿元"
    )

    history_group.add_argument(
        "--batch-lsjz","-bj", 
        action="store_true", 
        help="批量历史净值操作: 连续执行基金历史净值数据获取和分析操作"
    )


    # 3. 解析参数
    args = parser.parse_args()

    # 获取基金类型参数
    kind = args.kind
    if not kind in FUND_KINDS:
        kind = 'all'
    # 设置文件名
    fund_data_file = FUND_CACHE_FILE_PATH + '开放基金数据' + kind + '.csv'

    print('解析参数...')
    # 4. 使用参数（args 是一个对象，属性对应参数名）
    if args.get:
        # 获取基金数据并写入csv文件
        get_datas(file=fund_data_file,kind=kind,dx=args.dx)

    # 基金排行数据相关
    if args.show:
        #展示基金数据拍行列的最大值、最小值         
        if args.max_min:
            if os.path.exists(fund_data_file):
                df = pd.read_csv(fund_data_file,dtype={'基金代码':str})
            else:
                print('文件不存在，请先用-g参数获取数据')
                exit()

            # 需要分析的列
            columns_to_analyze = COLUMNS_TO_ANALYZE

            # 确保这些列是数值类型
            for col in columns_to_analyze:
                if col in df.columns:
                    df[col] = pd.to_numeric(df[col], errors='coerce')  # 将无法转换的值设为 NaN

            # 获取这些列的最大值和最小值
            stats = df[columns_to_analyze].agg(['max', 'min'])

            print("\n收益率统计数据（日增长率、近1周、近1月、近3月、近6月、近1年）：")
            print(stats)
        
        if args.filter_con != '':
            # 对基金数据，进行条件筛选和某列的排序
            filter_sort(file=fund_data_file,
                        condiston_str=args.filter_con.replace('\'',''),
                        sort_order=args.filter == 1)

        if args.sort_by_r2:
            #获取参数
            #分析各基金的增长率线性度，必须的列数，最小2，最大6
            if args.r2_num and args.r2_num >=2 and args.r2_num <=6:
                r2_num = args.r2_num
            else:
                r2_num = 5

            #分析各基金的增长率线性度，最后一列的最小增长率 单位%
            if args.r2_rates:
                r2_rates =  args.r2_rates
            else:
                r2_rates = 20
            
            # 分析各基金的增长率线性度，通过线性回归计算 R² 和 MSE 值来评估哪只基金的增长率更接近直线
            calculate_r2_ljsyl_fix(file=fund_data_file,col_num = r2_num,max_rates=r2_rates)
            # sort_by_r2_(col_num = r2_num,max_rates=r2_rates)

        if args.test:
            # 连续获取不同类型基金数据
            for test_kind in FUND_KINDS:
                #获取基金数据并写入csv文件
                fund_data_file = FUND_CACHE_FILE_PATH + '开放基金数据' + test_kind + '.csv'
                get_datas(file=fund_data_file,kind=test_kind,dx='1')
            #打印分析数据
            batch_print_r2()                
                
    # 历史数据
    if args.history:
        #获取历史收益率数据
        if args.his_get:
            fund_type = args.kind
            # 去掉‘all’这个分类
            if fund_type == 'all':
                fund_type = 'gp'
            duration_type = args.dtype
            get_ljsyzs_2csv(fund_type=fund_type,duration_type=duration_type)

        #获取历史净值数据
        if args.get_jz:
            fund_type = args.kind
            # 去掉‘all’这个分类
            if fund_type == 'all':
                fund_type = 'gp'
            batch_update_fund_data(fund_type=fund_type)

        #分析数据 - 累计收益率
        if args.sort_by_r2:
            fund_type = args.kind
            days = args.days
            min_value = args.min_value
            max_value = args.max_value
            n = args.show_number
            print(f"分析{fund_type}类型基金{days}天内的累计收益增长率最小{min_value}%最大{max_value}%的数据曲线")
            calculate_r2_ljsyl(fund_type=fund_type,min_value=min_value,max_value=max_value,from_days=days,show_number=n)

        #分析数据 - 历史净值数据
        if args.jz_r2:
            fund_type = args.kind
            days = args.days
            min_value = args.min_value
            max_value = args.max_value
            if args.jz_lj:
                value_column = 'LJJZ'
                value_column_str = '累计净值'
            else:                
                value_column = 'DWJZ'
                value_column_str = '单位净值'
            guimo = args.max_guimo
            n = args.show_number
            print(f"分析{fund_type}类型基金{days}天内的{value_column_str}最小{min_value}最大{max_value}的数据曲线")
            calculate_r2_lsjz(fund_type = fund_type, min_value = min_value,
                              max_value = max_value,from_days = days,
                              value_column = value_column, show_number = n,
                              max_guimo = guimo)


        # 批量获取数据  -- 测试
        if args.test:
            for type in ['hh','gp','zs','zq','qdii','fof']:
                get_ljsyzs_2csv(fund_type=type,duration_type='hy')
                #calculate_r2_ljsyl(fund_type=type,from_days=days)

        if args.batch_lsjz:
            for type in ['hh','gp','zs','zq','qdii','fof']:
                batch_update_fund_data(fund_type=type)
                #calculate_r2_ljsyl(fund_type=type,from_days=days)
    

if __name__ == "__main__":
    
    main()
    #fund_ljsyzs_2csv(fund_type='qdii')
    #batch_update_fund_data()
    #calculate_r2_ljsyl(fund_type='qdii',from_date=30)
    #FundService().get_ljsyzs('100050',duration_type='q')